package eu.catengine.graphic;

import java.nio.FloatBuffer;
import java.util.HashMap;

import javax.microedition.khronos.opengles.GL10;

import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.opengl.GLUtils;
import eu.catengine.common.iface.IDisposable;
import eu.catengine.primitive.Point;
import eu.catengine.primitive.Size;

/**
 * Klasa przechowująca dane dotyczące tekstur.
 * 
 * @author mbienkowski
 */
public class Texture implements IDisposable
{
    protected int[] mTexture = new int[1];
    protected HashMap<String, TextureRegion> mTextureRegionHashMap;

    protected Size mImageSize;
    protected Size mTextureSize;

    protected Texture(){}
    
    /**
     * Konstuktor domyślny. <br>
     * Obiekty tej klasy tworzone są automatycznie przez TextureManager. <br>
     * Nie należy tworzyć pojedynczych obiektów klasy bez udziału managera.
     */
    public Texture(GL10 gl, Bitmap bitmap)
    {
	mTextureRegionHashMap = new HashMap<String, TextureRegion>();

	float mRegionCoords[] = new float[] { 0.0f, 1.0f, // top left (V2)
		0.0f, 0.0f, // bottom left (V1)
		1.0f, 1.0f, // top right (V4)
		1.0f, 0.0f // bottom right (V3)
	};

	// Getting parameters from bitmap
	mImageSize = new Size(bitmap.getWidth(), bitmap.getHeight());

	int textureWidth = (int) mImageSize.width;
	int textureHeight = (int) mImageSize.height;

	// Checking if this is texture in meaning of openGL
	if (!isPowerOfTwo(textureWidth))
	    textureWidth = nextPowerOfTwo(textureWidth);
	if (!isPowerOfTwo(textureHeight))
	    textureHeight = nextPowerOfTwo(textureHeight);

	// Resizing bitmap to fit power of two dimensions
	if (textureWidth != bitmap.getWidth() || textureHeight != bitmap.getHeight())
	{
	    Bitmap tmpBitmap = Bitmap.createBitmap(textureWidth, textureHeight, Config.ARGB_8888);
	    Canvas canvas = new Canvas(tmpBitmap);

	    mRegionCoords[1] = (float) bitmap.getHeight() / (float) textureHeight;
	    mRegionCoords[5] = (float) bitmap.getHeight() / (float) textureHeight;

	    mRegionCoords[4] = (float) bitmap.getWidth() / (float) textureWidth;
	    mRegionCoords[6] = (float) bitmap.getWidth() / (float) textureWidth;

	    canvas.drawBitmap(bitmap, new Matrix(), null);
	    bitmap = tmpBitmap;
	}

	mTextureSize = new Size(textureWidth, textureHeight);

	// Generate one texture pointer and bind it to our array
	gl.glGenTextures(1, mTexture, 0);
	gl.glBindTexture(GL10.GL_TEXTURE_2D, mTexture[0]);

	// Create nearest filtered texture
	gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
	gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);

	// Use Android GLUtils to specify a two-dimensional texture image from
	// our bitmap
	GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);

	// Clean up
	bitmap.recycle();

	// Adding default regions to hash map
	mTextureRegionHashMap.put("default", new TextureRegion(mRegionCoords));
    }

    /**
     * Zwraca wskaźnik na tablicę.
     */
    public int getTexturePointer()
    {
	return mTexture[0];
    }

    /**
     * Zwraca bufor tekstury.
     */
    public FloatBuffer getTextureBuffer()
    {
	return mTextureRegionHashMap.get("default").mTextureBuffer;
    }

    /**
     * Zwraca bufor tekstury względem podanego regionu.
     * 
     * @param id
     *            <code>String</code> Nazwa regionu.
     * @return <code>FloatBuffer</code> Bufor regionu.
     */
    public FloatBuffer getTextureBuffer(String id)
    {
	try
	{
	    return mTextureRegionHashMap.get(id).mTextureBuffer;
	}
	catch (Exception e)
	{
	    e.printStackTrace();
	}
	return null;
    }

    /**
     * Zwraca rozmiar pliku graficznego, który został wczytany do tekstury.
     */
    public Size getSize()
    {
	return mImageSize;
    }

    /**
     * Dodaje region do tekstury o ile taki nie istnieje.
     * 
     * @param id
     *            <code>String</code> Nazwa.
     * @param point
     *            <code>Point</code> Punkt zaczepienia (lewy góry róg).
     * @param regionSize
     *            <code>Size</code> Rozmiar regionu.
     */
    public void addTextureRegion(String id, Point point, Size regionSize)
    {
	if (mTextureRegionHashMap.get(id) == null)
	{
	    mTextureRegionHashMap.put(id, new TextureRegion(point, regionSize, mTextureSize));
	}
    }

    /**
     * Dodaje region do tekstury o ile taki nie istnieje.
     * 
     * @param id
     *            <code>String</code> Nazwa
     * @param region
     *            <code>TextureRegion</code> Dodawany region.
     */
    public void addTextureRegion(String id, TextureRegion region)
    {
	if (mTextureRegionHashMap.get(id) == null)
	{
	    mTextureRegionHashMap.put(id, region);
	}
    }

    /**
     * Zwraca region tekstury.
     */
    public TextureRegion getTextureRegion(String textureRegionID)
    {
	return mTextureRegionHashMap.get(textureRegionID);
    }

    /**
     * Zwraca tablicę regionów dla animacji.<br>
     * Regiony muszą występować w poziomie.
     * 
     * @param frameSize
     *            <code>Size</code> Rozmiar regionu.
     * @param frames
     *            <code>int</code> Ilość klatek.
     * @return <code>TextureRegion[]</code> regiony tekstury odpowiedzialne za
     *         kolejne klatki animacji.
     */
    public TextureRegion[] getTextureRegions(Size frameSize, int frames)
    {
	float width = frameSize.width;

	TextureRegion regions[] = new TextureRegion[frames];

	for (int i = 0; i < frames; i++)
	{
	    regions[i] = new TextureRegion(new Point(i * width, 0), frameSize, mTextureSize);
	}

	return regions;
    }

    /**
     * Zwraca tablicę regionów dla animacji.
     * 
     * @param frameSize
     *            <code>Size</code> Rozmiar regionu.
     * @param frames
     *            <code>int</code> Ilość klatek.
     * @param framesInLine
     *            <code>int</code> Ilość klatek znajdujących się w jednym
     *            wierszu.
     * @return <code>TextureRegion[]</code> regiony tekstury odpowiedzialne za
     *         kolejne klatki animacji.
     */
    public TextureRegion[] getTextureRegions(Size frameSize, int frames, int framesInLine)
    {
	float width = frameSize.width;
	float height = frameSize.height;

	TextureRegion regions[] = new TextureRegion[frames];

	for (int i = 0; i < frames; i++)
	{
	    regions[i] = new TextureRegion(new Point((i % framesInLine) * width, (i / framesInLine) * height), frameSize, mTextureSize);
	}

	return regions;
    }

    protected int nextPowerOfTwo(int value)
    {
	if (value == 0)
	    return 1;
	if ((value & value - 1) == 0)
	    return value;
	value |= value >> 1;
	value |= value >> 2;
	value |= value >> 4;
	value |= value >> 8;
	value |= value >> 16;
	return value + 1;
    }

    protected boolean isPowerOfTwo(int value)
    {
	return value != 0 && (value & value - 1) == 0;
    }

    @Override
    public void dispose()
    {
	for (TextureRegion region : mTextureRegionHashMap.values())
	{
	    region.dispose();
	}

	mTextureRegionHashMap.clear();

	mTexture = null;
	mImageSize = null;
	mTextureSize = null;
    }

}